{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "954f6f45",
   "metadata": {},
   "source": [
    "## 创建数据库链接"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "c9e697ca",
   "metadata": {},
   "outputs": [],
   "source": [
    "import sqlite3\n",
    "# 链接到tutorial.db数据库(不存在时自动创建同名空数据库)\n",
    "con = sqlite3.connect(\"tutorial.db\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "58625c6d",
   "metadata": {},
   "source": [
    "## 获取数据库连接对象的游标cursor\n",
    "这个步骤通常可以省略,sqlite3库注意到从链接对象调用.cursor()的操作比较固定,所以针对cursor调用的方法一般都可以直接对连接对象con等效地直接调用\n",
    "事实上,被省略的创建cursor的这个过程被隐式的自动执行,从方便用户的使用\n",
    "\n",
    "> 然而connection.close()方法总是应该在数据库操作结束后手动调用,这个操作不会被自动执行,即便使用上下文管理器with语句也不例外\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "2210a7d4",
   "metadata": {},
   "outputs": [],
   "source": [
    "#获取cursor(通常可以省略)\n",
    "cur = con.cursor()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d1dedfb9",
   "metadata": {},
   "source": [
    "## 数据库事务\n",
    "什么是数据库事务？\n",
    "数据库事务（Transaction） 是访问和更新数据库的一个逻辑单元，它包含一个或多个数据库操作（如插入、更新、删除），这些操作要么全部成功执行 ，要么全部失败回滚 ，即“要么全做，要么不做 ”。\n",
    "\n",
    "数据库事务的四大特性：原子性（Atomicity）、一致性（Consistency）、隔离性（Isolation）、持久性（Durability）。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "360ff321",
   "metadata": {},
   "source": [
    "## 执行数据库语句"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "e2433c94",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<sqlite3.Cursor at 0x1e4b22c96c0>"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 执行建表语句(movie表),包含3个字段(title, year, score)\n",
    "cur.execute(\"CREATE TABLE movie(title, year, score)\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5c0f23b5",
   "metadata": {},
   "source": [
    "我们可以通过查询 SQLite 内置的 sqlite_matser 表以验证新表是否已经创建，本例中，此时该表应该已经包括了一条 movie 的表定义（更多内容请参考 The Schema Table ）。下面的代码将通过调用函数 cur.excute(...) 执行查询，把结果赋给 res ，而后调用 res.fetchone() 获取结果行"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0194c1f5",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('movie',)"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 通过查询slite_master中的name列看看此时数据库有多少个表\n",
    "res_cur = cur.execute(\"SELECT name FROM sqlite_master\")\n",
    "# 查看第一个表\n",
    "res_cur.fetchone()\n",
    "\n",
    "# 查看所有表\n",
    "# res.fetchall()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f22f7ceb",
   "metadata": {},
   "source": [
    "\n",
    "可以看到movie表被创建(不要连续执行res.fetchone()和res.fetchall())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "39e54a63",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[('movie',)]"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "res_cur = cur.execute(\"SELECT name FROM sqlite_master\")\n",
    "res_cur.fetchall()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ee7b4cc4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[('table', 'movie', 'movie', 2, 'CREATE TABLE movie(title, year, score)')]"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 查看sqlite_master所有字段\n",
    "res_cur=cur.execute(\"select * from sqlite_master\")\n",
    "res_cur.fetchall()\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "45b9192c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 查看某个尚不存在表\n",
    "res_cur = cur.execute(\"SELECT name FROM sqlite_master WHERE name='spam'\")\n",
    "res_cur.fetchone() is None"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "312d3815",
   "metadata": {},
   "source": [
    "## 插入数据\n",
    "这里使用隐式字段匹配,从而简写sql\n",
    "\n",
    "此外,INSERT 语句将隐式地创建一个事务 (transaction) ，事务需要在将更改保存到数据库前提交（更多细节请参考 事务控制 ）。\n",
    "\n",
    "我们通过在一个连接对象（本例中为 con）上调用 con.commit() 提交事务："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "f4b818bc",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<sqlite3.Cursor at 0x1e4b22c96c0>"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cur.execute(\"\"\"\n",
    "    INSERT INTO movie VALUES\n",
    "        ('Monty Python and the Holy Grail', 1975, 8.2),\n",
    "        ('And Now for Something Completely Different', 1971, 7.5)\n",
    "\"\"\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "6586e6d8",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 仅仅执行execute语句，并没有实际的提交到数据库中，需要调用链接对象con的commit()方法才会真正的提交到数据库中\n",
    "con.commit()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "36d7e4ce",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[(8.2,), (7.5,)]"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "res_cur = cur.execute(\"SELECT score FROM movie\")\n",
    "res_cur.fetchall()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "40eba410",
   "metadata": {},
   "source": [
    "上面的代码中，结果是一个包含了两个元组 (tuple) 的列表 (list) ，其中每一个元组代表一个数据行，每个数据行都包括该行的 score 值。\n",
    "现在，让我们调用 cur.executemany(...) 再插入三行数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "a0f08035",
   "metadata": {},
   "outputs": [],
   "source": [
    "data = [\n",
    "    (\"Monty Python Live at the Hollywood Bowl\", 1982, 7.9),\n",
    "    (\"Monty Python's The Meaning of Life\", 1983, 7.5),\n",
    "    (\"Monty Python's Life of Brian\", 1979, 8.0),\n",
    "]\n",
    "cur.executemany(\"INSERT INTO movie VALUES(?, ?, ?)\", data) # 每一个?号占位符会对应data列表(二维结构)中的一个元组(元素的列和movie表的列对应),一个元组表示一条数据记录行\n",
    "con.commit()  # 记得在执行 INSERT 之后提交事务。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4dd1c80c",
   "metadata": {},
   "source": [
    "请注意，占位符 (placeholders) ? 是用来在查询中绑定数据 data 的。在绑定 Python 的值到 SQL 语句中时，请使用占位符取代格式化字符串 (string formatting ) 以避免 SQL 注入攻击 （更多细节请参见 如何在 SQL 查询中使用占位符来绑定值 ）。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d35057bf",
   "metadata": {},
   "source": [
    "同样的，我们可以通过执行 SELECT 查询验证新的数据行是否已经插入表中，这一次我们将迭代查询的结果："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "f7ea356f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(1971, 'And Now for Something Completely Different')\n",
      "(1975, 'Monty Python and the Holy Grail')\n",
      "(1979, \"Monty Python's Life of Brian\")\n",
      "(1982, 'Monty Python Live at the Hollywood Bowl')\n",
      "(1983, \"Monty Python's The Meaning of Life\")\n"
     ]
    }
   ],
   "source": [
    "for row in cur.execute(\"SELECT year, title FROM movie ORDER BY year\"):\n",
    "    print(row)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "42273c6a",
   "metadata": {},
   "source": [
    "如上可见，每一行都是包括 (year,title) 这两个元素的元组 (tuple ) ，它与我们查询中选中的数据列相匹配。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "82738aa5",
   "metadata": {},
   "source": [
    "## 将数据写入磁盘文件🎈\n",
    "最后，让我们先通过调用 con.close() 关闭现存的与数据库的连接，而后打开一个新的连接、创建一个新的游标、执行一个新的查询以验证我们是否将数据库写入到了本地磁盘上：\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "06e8c884",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 关闭此次数据库链接,将数据更改应用到db文件中(在调用con.close之前,数据变更都暂存在内存中)\n",
    "con.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a0ee71d8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The highest scoring Monty Python movie is 'Monty Python and the Holy Grail', released in 1975\n"
     ]
    }
   ],
   "source": [
    "new_con = sqlite3.connect(\"tutorial.db\")\n",
    "new_cur = new_con.cursor()\n",
    "res_cur = new_cur.execute(\"SELECT title, year FROM movie ORDER BY score DESC\")\n",
    "title, year = res_cur.fetchone()\n",
    "\n",
    "print(f\"The highest scoring Monty Python movie is {title!r}, released in {year}\")\n",
    "# {title!r} 表示将变量 title 的值以 repr() 形式插入到字符串中(字符串title会带上引号)。\n",
    "\n",
    "new_con.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "01d1eadb",
   "metadata": {},
   "source": [
    "- 阅读 [常用方案指引](https://docs.python.org/zh-cn/3.13/library/sqlite3.html#sqlite3-howtos) 以获取更多信息：\n",
    "  - [如何在 SQL 查询中使用占位符来绑定值](https://docs.python.org/zh-cn/3.13/library/sqlite3.html#sqlite3-placeholders)\n",
    "  - [如何将自定义 Python 类型适配到 SQLite 值](https://docs.python.org/zh-cn/3.13/library/sqlite3.html#sqlite3-adapters)\n",
    "  - [如何将 SQLite 值转换为自定义 Python 类型](https://docs.python.org/zh-cn/3.13/library/sqlite3.html#sqlite3-converters)\n",
    "  - [如何使用连接上下文管理器](https://docs.python.org/zh-cn/3.13/library/sqlite3.html#sqlite3-connection-context-manager)\n",
    "  - [如何创建并使用行工厂对象](https://docs.python.org/zh-cn/3.13/library/sqlite3.html#sqlite3-howto-row-factory)\n",
    "- 参阅 [说明](https://docs.python.org/zh-cn/3.13/library/sqlite3.html#sqlite3-explanation) 以获取关于事务控制的更深一步的背景。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b434d3b5",
   "metadata": {},
   "source": [
    "## 如何在 SQL 查询中使用占位符来绑定值\n",
    "\n",
    "SQL 操作通常会需要使用来自 Python 变量的值。 不过，请谨慎使用 Python 的字符串操作来拼装查询，因为这样易受 SQL injection attacks。 \n",
    "例如，攻击者可以简单地添加结束单引号并注入 OR TRUE 来选择所有的行:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ae6bce67",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 绝不要这样做 -- 很不安全！\n",
    "symbol = input()\n",
    "\n",
    "sql = \"SELECT * FROM stocks WHERE symbol = '%s'\" % symbol\n",
    "print(sql)\n",
    "\n",
    "cur.execute(sql)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b2760f60",
   "metadata": {},
   "source": [
    "请改用 DB-API 的形参替换。 要将变量插入到查询字符串中，可在字符串中使用占位符，并通过将实际值作为游标的 execute() 方法的第二个参数以由多个值组成的 tuple 形式提供给查询来替换它们。\n",
    "\n",
    "SQL 语句可以使用两种占位符之一：\n",
    "问号占位符（问号风格）或命名占位符（命名风格）。 \n",
    "1. 对于问号风格，parameters 要是一个长度必须与占位符的数量相匹配的 sequence，否则将引发 ProgrammingError。 \n",
    "2. 对于命名风格，parameters 必须是 dict （或其子类）的实例，它必须包含与所有命名参数相对应的键；任何额外的条目都将被忽略。\n",
    "\n",
    "下面是一个同时使用这两种风格的示例："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9f2d37f4",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[('C', 1972)]\n"
     ]
    }
   ],
   "source": [
    "# 创建一个临时的内存中的数据库来演示\n",
    "con = sqlite3.connect(\":memory:\")\n",
    "\n",
    "cur = con.execute(\"CREATE TABLE lang(name, first_appeared)\")\n",
    "\n",
    "# 1.这是用于 executemany() 的名称风格：\n",
    "data = (\n",
    "    {\"name\": \"C\", \"year\": 1972},\n",
    "    {\"name\": \"Fortran\", \"year\": 1957},\n",
    "    {\"name\": \"Python\", \"year\": 1991},\n",
    "    {\"name\": \"Go\", \"year\": 2009},\n",
    ")\n",
    "# 和data中各条记录字典的字段名(name,year)对应,这里插入的sql模板中使用对应的名字,但是要在名字前追加`:`号,来表明data中的记录是字典而不是元组\n",
    "cur.executemany(\"INSERT INTO lang VALUES(:name, :year)\", data)\n",
    "\n",
    "# 2.这是用于 SELECT 查询的问号风格：\n",
    "params = (1972,)\n",
    "cur.execute(\"SELECT * FROM lang WHERE first_appeared = ?\", params)\n",
    "# 这里我们需要查询最早出现于1972年的变成语言,where first_appeared='1972',用更加灵活的方式就是上面的这种占位符形式,但是注意参数是元组,即便只有一个元素,也要包装成元组\n",
    "\n",
    "# 查看操作结果\n",
    "print(cur.fetchall())\n",
    "con.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "32b32e95",
   "metadata": {},
   "source": [
    "## 如何使用连接快捷方法(省略cursor的创建)\n",
    "通过使用 Connection 类的 execute(), executemany() 与 executescript() 方法，您可以**简化您的代码，因为无需再显式创建 （通常是多余的） Cursor 对象**。\n",
    "此时 `Cursor` 对象**会被隐式创建并且由这些快捷方法返回**。\n",
    "这样一来，您仅需在 Connection 对象上调用一次方法就可以执行 SELECT 语句，并对其进行迭代。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "a96f6d4c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "('C++', 1985)\n",
      "('Objective-C', 1984)\n",
      "I just deleted 2 rows\n"
     ]
    }
   ],
   "source": [
    "# 创建并填充表。\n",
    "con = sqlite3.connect(\":memory:\")\n",
    "con.execute(\"CREATE TABLE lang(name, first_appeared)\")\n",
    "data = [\n",
    "    (\"C++\", 1985),\n",
    "    (\"Objective-C\", 1984),\n",
    "]\n",
    "con.executemany(\"INSERT INTO lang(name, first_appeared) VALUES(?, ?)\", data)\n",
    "\n",
    "# 打印表内容\n",
    "for row in con.execute(\"SELECT name, first_appeared FROM lang\"):\n",
    "    print(row)\n",
    "\n",
    "print(\"I just deleted\", con.execute(\"DELETE FROM lang\").rowcount, \"rows\")\n",
    "\n",
    "# close() 不是一个快捷方法也不会被自动调用；\n",
    "# 连接对象应当被手动关闭\n",
    "con.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "808a47ae",
   "metadata": {},
   "source": [
    "## 如何使用连接上下文管理器(with)\n",
    "Connection 对象可**被用作上下文管理器**以便在离开上下文管理器**代码块时自动提交或回滚开启的事务**。(但是连接对象并不会自动关闭,仍然需要手动关闭)\n",
    "- 如果 with 语句体无异常地结束，事务将被提交。 \n",
    "- 如果提交失败，或者如果 with 语句体引发了未捕获的异常，则事务将被回滚。 如果 autocommit 为 False，则会在提交或回滚后隐式地开启一个新事务。\n",
    "\n",
    "如果在离开 with 语句体时没有开启的事务，或者如果 autocommit 为 True，则上下文管理器将不做任何操作。\n",
    "> 上下文管理器既不会隐式开启新事务也不会关闭连接。 如果你需要关闭上下文管理器，请考虑使用 contextlib.closing()。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "83d5acaa",
   "metadata": {},
   "outputs": [],
   "source": [
    "con = sqlite3.connect(\":memory:\")\n",
    "con.execute(\"CREATE TABLE lang(id INTEGER PRIMARY KEY, name VARCHAR UNIQUE)\")\n",
    "\n",
    "# 操作1:通常会成功，con.commit() 将在此后被自动调用\n",
    "with con:\n",
    "    con.execute(\"INSERT INTO lang(name) VALUES(?)\", (\"Python\",))\n",
    "\n",
    "#操作2:由于上述创建表的语句制定了name必须唯一,从而下面的重复插入已经存在的值会失败\n",
    "# con.rollback() 会在 with 代码块结束时被自动调用,并附带一个异常；\n",
    "# 该异常仍会被引发并且必须被捕获\n",
    "try:\n",
    "    with con:\n",
    "        con.execute(\"INSERT INTO lang(name) VALUES(?)\", (\"Python\",))\n",
    "except sqlite3.IntegrityError:\n",
    "    print(\"couldn't add Python twice\")\n",
    "\n",
    "# 被用作上下文管理器的连接对象只能[提交或回滚]事务，\n",
    "# 因此连接对象必须被手动关闭\n",
    "con.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "74b2db96",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "couldn't add Python twice\n"
     ]
    }
   ],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "333aa707",
   "metadata": {},
   "outputs": [],
   "source": [
    "con.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "1a498bad",
   "metadata": {},
   "outputs": [],
   "source": [
    "con.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "0d22b547",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Cursor closed correctly: Cannot operate on a closed cursor.\n",
      "Connection closed correctly: Cannot operate on a closed database.\n"
     ]
    }
   ],
   "source": [
    "import sqlite3\n",
    "\n",
    "conn = sqlite3.connect(':memory:')\n",
    "cursor = conn.cursor()\n",
    "cursor.close()\n",
    "conn.close()\n",
    "\n",
    "try:\n",
    "    cursor.execute(\"SELECT 1\")  # 已关闭，应抛出异常\n",
    "except Exception as e:\n",
    "    print(\"Cursor closed correctly:\", e)\n",
    "\n",
    "try:\n",
    "    conn.cursor()  # 已关闭，应抛出异常\n",
    "except Exception as e:\n",
    "    print(\"Connection closed correctly:\", e)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "dc24d76d",
   "metadata": {},
   "outputs": [],
   "source": [
    "import sqlite3\n",
    "c=None\n",
    "with sqlite3.connect(':memory:') as conn:\n",
    "    c=conn.cursor()\n",
    "    c.execute(\"CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)\")\n",
    "    c.execute(\"INSERT INTO users (name, email) VALUES ('John', 'john@example.com')\")\n",
    "\n",
    "# conn.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "a1c86c52",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<sqlite3.Cursor at 0x270994b6f40>"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "conn.cursor()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "c8483d2b",
   "metadata": {},
   "outputs": [],
   "source": [
    "conn.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "2747cfa1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(1, 'Alice')\n",
      "(2, 'Bob')\n",
      "(3, 'Charlie')\n",
      "(4, 'David')\n",
      "(5, 'Eve')\n"
     ]
    }
   ],
   "source": [
    "import sqlite3\n",
    "\n",
    "with sqlite3.connect(\"example.db\") as conn:\n",
    "    res_cur = conn.execute(\"SELECT * FROM users\")  # 假设有一个users表\n",
    "    rows = res_cur.fetchall()\n",
    "    for row in rows:\n",
    "        print(row)\n",
    "\n",
    "# conn.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "708c41a4",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<sqlite3.Cursor at 0x270997c6840>"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "conn.cursor() #如果成功返回对象,说明前面创建的连接对象没有被关闭,需要手动关闭"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "0306cb44",
   "metadata": {},
   "outputs": [],
   "source": [
    "conn.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "b26e5c09",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "conn已被关闭\n"
     ]
    }
   ],
   "source": [
    "try:\n",
    "    c=conn.cursor()\n",
    "    print(\"conn没有被关闭\",c)\n",
    "except Exception as e:\n",
    "    print(\"conn已被关闭\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3d98369f",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "base",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
